defmodule IocpMonitor.Worker.DataBuffer do
  require Logger

  use GenServer
  alias IocpMonitor.{DeviceAddressHolders, DataBuffers}
  alias IocpMonitor.Worker.DeviceAddressHolder

  @capacity 60

  def start_link({client, sup}) do
    device_address = get_device_address(sup)
    GenServer.start_link(__MODULE__, {client, sup, []}, name: name_for(device_address))
  end

  def push(device_address, data) do
    GenServer.call(name_for(device_address), {:push, data})
  end

  def peek(device_address) do
    case Registry.lookup(DataBuffers, device_address) do
      [{pid, _}] ->
        GenServer.call(pid, :peek)
      [] ->
        :empty
    end
  end

  @impl true
  def init(state) do
    {:ok, state}
  end

  @impl true
  def handle_call({:push, data}, _from, {client, sup, old_data}) when length(old_data) >= @capacity do
    dump(old_data)
    {:reply, :ok, {client, sup, [data]}}
  end

  def handle_call({:push, data}, _from, {client, sup, old_data}) do
    {:reply, :ok, {client, sup, [data | old_data]}}
  end

  def handle_call(:peek, _from, {_client, _sup, [head | _]} = state) do
    {:reply, {:ok, head}, state}
  end

  def handle_call(:peek, _from, {_client, _sup, []} = state) do
    {:reply, :empty, state}
  end

  @impl true
  def terminate(reason, {client, sup, data}) do
    dump(data)
    :gen_tcp.close(client)
    IocpMonitor.Client.Supervisor.terminate_child(sup)
  end

  defp name_for(device_address) do
    {:via, Registry, {DataBuffers, device_address}}
  end

  defp dump([data|_]) do
    BlockingQueue.push(BlockingQueue, data)
  end

  defp dump([]), do: nil

  defp get_device_address(sup) do
    [{device_address_holder, _}] = Registry.lookup(DeviceAddressHolders, sup)
    DeviceAddressHolder.get(device_address_holder)
  end

  @doc false
  def child_spec(arg) do
    %{
      id: __MODULE__,
      start: {__MODULE__, :start_link, [arg]},
      type: :worker,
      restart: :temporary,
      shutdown: 500
    }
  end
end
